package com.legendframework.core.cmd;

import com.legendframework.core.aop.utils.AopUtils;
import com.legendframework.core.cmd.annotation.Command;
import com.legendframework.core.cmd.annotation.*;
import com.legendframework.core.cmd.exception.BeyondPreselectionException;
import com.legendframework.core.cmd.exception.ParamerConverterNotFoundException;
import com.legendframework.core.cmd.exception.ParameterLimitException;
import com.legendframework.core.cmd.paramer.converter.ParamerConverter;
import com.legendframework.core.AbstractLegendPlugin;
import com.legendframework.core.dao.Return;
import com.legendframework.core.dao.support.ClassUtils;
import com.legendframework.core.ioc.FieldIParameter;
import com.legendframework.core.ioc.IParameter;
import com.legendframework.core.ioc.MethodIParameter;
import com.legendframework.core.support.lang3.StringUtils;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Auther: 196
 * @Description: 指令控制器
 */
public class CommandManager implements CommandExecutor {

    private AbstractLegendPlugin plugin;

    /**
     * 所有指令模板
     */
    private final List<ICommand> commands = new ArrayList<>();

    public List<ICommand> getCommands() {
        return commands;
    }

    public AbstractLegendPlugin getPlugin() {
        return plugin;
    }

    public CommandManager(AbstractLegendPlugin plugin) {
        this.plugin = plugin;
        plugin.getStore().getCommandManagerStore().getManagerPool().put(plugin.getClass(),this);
    }

    @Override
    public boolean onCommand(CommandSender sender, org.bukkit.command.Command command, String label, String[] args) {
        //空格处理
        args = specialTreatment(args);

        LinkedList<AlternativeCmd> alternatives = new LinkedList<>();
        for (ICommand impl : commands) {
            Class<?> implClass = AopUtils.getTargetClass(impl);
            if (implClass == null) {
                implClass = impl.getClass();
            }
            //这个指令的类注解
            Command commandAnnotation = implClass.getAnnotation(Command.class);

            if (commandAnnotation == null) {
                continue;
            }

            for (Method method : ClassUtils.getMethodsByClass(implClass)) {
                //获取不到cmd注解说明不是一个指令函数
                if(!method.isAnnotationPresent(Cmd.class)) {
                    continue;
                }

                //获取cmd
                Cmd cmdAnnotation = method.getAnnotation(Cmd.class);

                //没有cmd注解 跳出
                if(cmdAnnotation.value().length == 0) {
                    continue;
                }

                //判断是否为只允许玩家
                if(method.isAnnotationPresent(OnlyPlayer.class)){
                    //判断是否为玩家
                    if(!(sender instanceof Player)){
                        //不是玩家,跳出
                        continue;
                    }
                }

                //判断是否需要OP
                if (method.isAnnotationPresent(OnlyOp.class)) {
                    if (!sender.isOp()) {
                        //不是OP,跳出
                        continue;
                    }
                }

                //判断是否为只允许服务器
                if(method.isAnnotationPresent(OnlyServer.class)){
                    //判断是否为服务器
                    if(sender instanceof Player){
                        //是玩家,跳出
                        continue;
                    }
                }

                //判断权限
                if(sender instanceof Player){
                    Player p= (Player) sender;
                    if (method.isAnnotationPresent(Permission.class)) {
                        //判断是否需要权限
                        String perm = getPermission(method.getAnnotation(Permission.class), plugin.getRootCmd(), commandAnnotation, cmdAnnotation);
                        if(!p.hasPermission(perm)){
                            //权限不足 跳出
                            continue;
                        }
                    }
                }

                //获取方法中带注释的参数数量
                int numberOfInstructionPlaceholders = getNumberOfInstructionPlaceholders(method);

                //获取方法末尾的非必填参数的数量
                int numberOfVariablePlaceholders = getNumberOfVariablePlaceholders(method);

                for (String cmd : cmdAnnotation.value()) {
                    //指令匹配
                    boolean isSuccess = false;

                    List<String> params = new ArrayList<>();

                    two:
                    for (int j = 0; j < (numberOfVariablePlaceholders + 1); j++) {
                        String cmdStr = cmd;
                        //将根指令与方法指令进行拼接
                        StringBuilder cmdBuilder = new StringBuilder((commandAnnotation.cmd() + " " + cmdStr));
                        for (int i = 0; i < numberOfInstructionPlaceholders - j; i++) {
                            cmdBuilder.append(" ?");
                        }
                        cmdStr = cmdBuilder.toString().replace("  ", " ");
                        if (cmdStr.startsWith(" ")) {
                            cmdStr = cmdStr.substring(1, cmdStr.length());
                        }
                        if (cmdStr.endsWith(" ")) {
                            cmdStr = cmdStr.substring(0, cmdStr.length() - 1);
                        }

                        //通过空格切割得到指令参数
                        String[] cmdArgs = cmdStr.split(("\\s+"));

                        //判断是否用户只输入了一个根指令
                        if (cmdArgs.length == 1 && args.length == 0 && StringUtils.equals(cmdArgs[0], "")) {
                            isSuccess = true;
                            break;
                        }

                        //参数个数不匹配则校验下一条CMD
                        if (cmdArgs.length != args.length) {
                            continue;
                        }

                        //校验单个参数
                        for (int i = 0; i < cmdArgs.length; i++) {
                            //如果不是通配符 并且参数不匹配则跳出
                            if (!cmdArgs[i].equalsIgnoreCase(args[i]) && !StringUtils.equals(cmdArgs[i], "?"))
                                continue two;
                            if (StringUtils.equals(cmdArgs[i], "?")) params.add(args[i]);
                        }

                        //表示校验通过 跳出循环
                        isSuccess = true;
                        break;
                    }

                    if (isSuccess) {

                        //构建方法入参
                        Return<Object[], Runnable> runnableReturn = buildParametrs(sender, command, impl, method, params.toArray(new String[0]));

                        //如果当前指令带有参数，则进入备选区
                        if (params.size() > 0) {
                            alternatives.addLast(new AlternativeCmd(impl, method, runnableReturn));
                            continue;
                        }

                        execute(sender, impl, method, runnableReturn.getR1());
                        return true;
                    }

                }
            }
        }

        AlternativeCmd alternativeCmd = null;
        if (alternatives.size() == 1) {
            alternativeCmd = alternatives.getFirst();
        } else if (alternatives.size() > 1){
            List<AlternativeCmd> collect = alternatives.stream().filter(item -> item.params.getR1() != null).collect(Collectors.toList());
            if (collect.size() == 1){
                alternativeCmd = collect.get(0);
            } else if (collect.size() > 1) {
                throw new BeyondPreselectionException("找到多个符合条件的指令, 但可能是一个方法由于必填项导致一个方法的多种参数传入方式， 如看到这种报错， 开发者应该避免这种问题的出现: [" + StringUtils.join(args," ") +"]");
            } else {
                AlternativeCmd last = alternatives.getLast();
                last.params.getR2().run();
                return true;
            }
        }
        if (alternativeCmd != null) {
            if (alternativeCmd.params.getR2() != null) {
                //参数错误
                alternativeCmd.params.getR2().run();
                return true;
            }
            execute(sender,alternativeCmd.impl,alternativeCmd.method,alternativeCmd.params.getR1());
            return true;
        }
        return false;
    }

    /**
     * 执行指令
     * @param impl 指令实现类
     * @param method 指令具体方法
     * @param params 方法参数
     */
    private void execute(CommandSender sender , ICommand impl , Method method , Object[] params){
        try {
            //异步执行
            if (method.getAnnotation(Cmd.class).isAsyn()) {
                new BukkitRunnable() {
                    @Override
                    public void run() {
                        try {
                            method.invoke(impl,params);
                            impl.successHandle(sender,method,params);
                        } catch (IllegalArgumentException e){
                            throw new IllegalArgumentException("编码级错误，方法: '"+method+"' , 设置参数时，参数转换异常");
                        } catch (Throwable e) {
                            //执行失败处理器
                            impl.failHandle(sender,e,method);
                        }
                    }
                }.runTaskAsynchronously(plugin);
                return;
            }

            //执行指令方法
            method.invoke(impl,params);
            //执行成功处理器
            impl.successHandle(sender,method,params);
        } catch (IllegalArgumentException e){
            throw new IllegalArgumentException("编码级错误，方法: '"+method+"' , 设置参数时，参数转换异常");
        } catch (Throwable e) {
            //执行失败处理器
            impl.failHandle(sender,e,method);
        }
    }

    /**
     * 处理参数中的特殊空格
     * @param args
     * @return
     */
    private String[] specialTreatment(String[] args){
        //参数重组
        List<String> argsList = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            StringBuilder arg = new StringBuilder(args[i]);
            if (!arg.toString().startsWith("{")) {
                argsList.add(arg.toString());
                continue;
            }
            //判断有没有结尾符号 '}'
            boolean flag = false;
            int j = i+1;
            for (; j < args.length; j++){
                String argJ = args[j];
                if (argJ.endsWith("}")) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                argsList.add(arg.toString());
                continue;
            }
            for (int k = i +1 ; k <= j ; k++){
                arg.append(" ").append(args[k]);
            }
            i = j;
            argsList.add(arg.toString());
        }
        return argsList.toArray(new String[0]);
    }

    /**
     * 获取这个方法参数中指令参数占位符的个数
     * @param method 指令方法
     * @return
     */
    private int getNumberOfInstructionPlaceholders(Method method){
        //入参数注解数量
        int paramsCount = Math.toIntExact(Arrays.stream(method.getParameters()).filter(item -> item.isAnnotationPresent(CmdParam.class)).count());
        //入参类中的字段注解数
        for (Parameter parameter : method.getParameters()) {
            if (parameter.isAnnotationPresent(CmdEntityParam.class)) {
                List<Field> fields = ClassUtils.getByClass(parameter.getType());
                for (Field field : fields) {
                    if (field.isAnnotationPresent(CmdParam.class)) {
                        paramsCount++;
                    }
                }
            }
        }
        return paramsCount;
    }

    /**
     * 获取方法末尾的非必填参数的数量
     * @param method
     * @return
     */
    private int getNumberOfVariablePlaceholders(Method method){
        int count = 0;
        List<Parameter> collect = Arrays.stream(method.getParameters()).filter(item -> item.isAnnotationPresent(CmdParam.class) || item.isAnnotationPresent(CmdEntityParam.class)).collect(Collectors.toList());
        for (int i = collect.size() - 1; i >= 0; i--) {
            Parameter parameter = collect.get(i);
            if (parameter.isAnnotationPresent(CmdEntityParam.class)) {
                List<Field> classFields = ClassUtils.getByClass(parameter.getType()).stream().filter(item -> item.isAnnotationPresent(CmdParam.class)).collect(Collectors.toList());
                for (int j = classFields.size() - 1; j >= 0; j--) {
                    Field field = classFields.get(j);
                    if (!field.getAnnotation(CmdParam.class).required()){
                        count++;
                    }else {
                        break;
                    }
                }
            } else if (parameter.isAnnotationPresent(CmdParam.class) && !parameter.getAnnotation(CmdParam.class).required()) {
                count++;
            } else {
                break;
            }
        }
        return count;
    }

    /**
     * 构建这个方法的实参列表
     * @param impl 指令类
     * @param method 方法
     * @param args 指令参数
     * @return
     */
    private Return<Object[],Runnable> buildParametrs(CommandSender sender , org.bukkit.command.Command command, ICommand impl , Method method , String... args){
        int argsIndex = 0;
        Object[] params = new Object[method.getParameters().length];
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = method.getParameters()[i];
            if (parameter.isAnnotationPresent(CmdEntityParam.class)) {
                Object entityParams;
                try {
                    entityParams = parameter.getType().getConstructor().newInstance();
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
                    throw new IllegalArgumentException("指令方法中的参数无法实例化: "+method);
                }
                List<Field> fields = ClassUtils.getByClass(parameter.getType());
                for (Field field : fields) {
                    if (field.isAnnotationPresent(CmdParam.class)) {
                        //由于非必填，这里跳过
                        if (args.length <= argsIndex){
                            continue;
                        }
                        Object result;
                        FieldIParameter fieldIParameter = new FieldIParameter(field);
                        try {
                            result = castValue(sender, args[argsIndex],fieldIParameter);
                        } catch (Throwable e) {
                            final int finalArgsIndex = argsIndex;
                            return Return.build(null,() -> impl.paramsErrorHandle(sender,e,method,fieldIParameter,args[finalArgsIndex], finalArgsIndex));
                        }
                        toObjectField(method,entityParams,field,result);
                        argsIndex ++;
                    } else if (CommandSender.class.isAssignableFrom(field.getType())) {
                        toObjectField(method,entityParams,field,sender);
                    } else if (org.bukkit.command.Command.class.isAssignableFrom(field.getType())) {
                        toObjectField(method,entityParams,field,command);
                    }
                }
                params[i] = entityParams;
            } else if (parameter.isAnnotationPresent(CmdParam.class)) {
                //由于非必填，这里跳过
                if (args.length <= argsIndex){
                    continue;
                }
                MethodIParameter methodIParameter = new MethodIParameter(parameter);
                try {
                    params[i] = castValue(sender,args[argsIndex],methodIParameter);
                } catch (Throwable e) {
                    final int finalArgsIndex = argsIndex;
                    return Return.build(null,() -> impl.paramsErrorHandle(sender,e,method,methodIParameter,args[finalArgsIndex], finalArgsIndex));
                }
                argsIndex ++;
            } else if (CommandSender.class.isAssignableFrom(parameter.getType())) {
                params[i] = sender;
            } else if (org.bukkit.command.Command.class.isAssignableFrom(parameter.getType())) {
                params[i] = command;
            }
        }
        return Return.build(params,null);
    }

    /**
     * 注入字段
     */
    private void toObjectField(Method method ,Object obj , Field field , Object result){
        try {
            Method setterMethod = ClassUtils.getSetterMethodByField(obj.getClass(), field);
            setterMethod.invoke(obj,result);
        } catch (Throwable e) {
            throw new IllegalArgumentException("指令方法中的参数的字段无法注入，请检查字段是否提供了set方法: "+method);
        }
    }

    /**
     * 转换一个参数
     * @param sender 指令发送者
     * @param message 指令消息的单个需要转换的参数
     * @param parameter 参数
     * @return
     * @throws Throwable 抛出转换错误的异常
     */
    private Object castValue(CommandSender sender , String message , IParameter parameter) throws Throwable {
        if (message.startsWith("{") && message.endsWith("}")) {
            message = message.substring(1,message.length());
            message = message.substring(0,message.length()-1).trim();
        }
        Class<?> paramsClass = parameter.getType();
        Type parameterizedType = parameter.getParameterizedType();
        CollectionSize collectionSize = parameter.getAnnotation(CollectionSize.class);
        //集合大小校验
        String[] split = new String[0];
        if (StringUtils.isNotEmpty(message) && (paramsClass.isArray() || List.class.equals(paramsClass) || Set.class.equals(paramsClass))){
            split = message.split(" ");
            if (collectionSize != null && split.length > collectionSize.max()) {
                throw new ParameterLimitException(collectionSize.message());
            }
        }
        //根据类型注入参数
        if (paramsClass.isArray()) {
            Class<?> componentType = paramsClass.getComponentType();
            if (split.length == 0) {
                return Array.newInstance(componentType,0);
            }
            List<Object> collect = Arrays.stream(split).map(item -> stringCastType(sender, item, componentType, parameter)).collect(Collectors.toList());
            Object array = Array.newInstance(componentType,collect.size());
            for (int i = 0; i < collect.size(); i++) {
                Array.set(array, i, collect.get(i));
            }
            return array;
        }
        if (List.class.equals(paramsClass)) {
            if (split.length == 0) {
                return new ArrayList<>(0);
            }
            //如果是List集合
            if (List.class.equals(parameterizedType)) {
                //没有泛型
                return Arrays.stream(split).map(item -> stringCastType(sender,item, String.class,parameter)).collect(Collectors.toList());
            }else {
                //有泛型
                Class<?> actualTypeArgument = (Class<?>) ((ParameterizedType) parameterizedType).getActualTypeArguments()[0];
                return Arrays.stream(split).map(item -> stringCastType(sender,item, actualTypeArgument,parameter)).collect(Collectors.toList());
            }
        }
        if (Set.class.equals(paramsClass)) {
            if (split.length == 0) {
                return new HashSet<>(0);
            }
            //如果是Set集合
            if (Set.class.equals(parameterizedType)) {
                //没有泛型
                return Arrays.stream(split).map(item -> stringCastType(sender,item, String.class,parameter)).collect(Collectors.toSet());
            }else {
                //有泛型
                Class<?> actualTypeArgument = (Class<?>) ((ParameterizedType) parameterizedType).getActualTypeArguments()[0];
                return Arrays.stream(split).map(item -> stringCastType(sender,item, actualTypeArgument,parameter)).collect(Collectors.toSet());
            }
        } else {
            return stringCastType(sender,message,paramsClass,parameter);
        }
    }

    /**
     * 获取基本数据类型的值转换
     * @param param str类型的参数
     * @return
     */
    private Object stringCastType(CommandSender sender, String param,Class type , IParameter parameter){
        for (ParamerConverter<?> paramerConverter : plugin.getStore().getCommandManagerStore().getParamerConverters()) {
            if (paramerConverter.check(type)) {
                return paramerConverter.castParameter(sender, param, type, parameter);
            }
        }
        throw new ParamerConverterNotFoundException("对应的转换器未找到");
    }

    /**
     * 符合条件的指令备选
     */
    private static class AlternativeCmd{

        private ICommand impl;

        private Method method;

        private Return<Object[], Runnable> params;

        public AlternativeCmd(ICommand impl, Method method, Return<Object[], Runnable> params) {
            this.impl = impl;
            this.method = method;
            this.params = params;
        }
    }

    /**
     * 构建权限信息
     * @return
     */
    public static String getPermission(Permission permission , String rootCmd , Command command , Cmd cmd){
        if (permission == null || command == null || cmd == null || StringUtils.isEmpty(rootCmd)) {
            return null;
        }
        String perm = permission.value();
        if (StringUtils.isEmpty(permission.value())) {
            //解析指令
            String s = rootCmd + " " + command.cmd() + " " + cmd.value()[0];
            perm = s.replace("  "," ").trim().replace(" ",".");
        }
        return perm;
    }
}

